/* proto.c
 * Routines for protocol tree
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <glib.h>
#include <float.h>
#include <inttypes.h>
#include <errno.h>

#include "register-int.h"

#include "wmem_strbuf.h"
#include "wmem_strutils.h"

#include "ftypes.h"
#include "sign_ext.h"
#include "tvbuff.h"
#include "packet.h"
#include "strutil.h"
#include "unit_strings.h"
#include "bits_ctz.h"
#include "bits_count_ones.h"

#include "lua_dissector.h"
#include "lua_dissect.h"

#include "proto.h"

#include "utf8_entities.h"

/* Ptvcursor limits */
#define SUBTREE_ONCE_ALLOCATION_NUMBER 8
#define SUBTREE_MAX_LEVELS 256

#define max_tree_items 32

typedef struct __subtree_lvl {
    int         cursor_offset;
    proto_item *it;
    proto_tree *tree;
} subtree_lvl;

struct ptvcursor {
//    wmem_allocator_t *scope;
    subtree_lvl *pushed_tree;
    uint8_t         pushed_tree_index;
    uint8_t         pushed_tree_max;
    proto_tree  *tree;
    tvbuff_t    *tvb;
    int         offset;
};

#define cVALS(x) (const value_string*)(x)

/** See inlined comments.
 @param tree the tree to append this item to
 @param free_block a code block to call to free resources if this returns
 @return NULL if 'tree' is null */
#define CHECK_FOR_NULL_TREE_AND_FREE(tree, free_block)            \
    if (!tree) {                            \
        free_block;                        \
        return NULL;                        \
    }

/** See inlined comments.
 @param tree the tree to append this item to
 @param free_block a code block to call to free resources if this returns
 @return NULL if 'tree' is null */
#define CHECK_FOR_NULL_TREE(tree) \
    CHECK_FOR_NULL_TREE_AND_FREE(tree, ((void)0))

/** See inlined comments.
 @param length the length of this item
 @param cleanup_block a code block to call to free resources if this returns
 @return NULL if 'length' is lower -1 or equal 0 */
#define CHECK_FOR_ZERO_OR_MINUS_LENGTH_AND_CLEANUP(length, cleanup_block)    \
    if (length < -1 || length == 0 ) {                \
        cleanup_block;                        \
        return NULL;                        \
    }

/** See inlined comments.
 @param length the length of this item
 @return NULL if 'length' is lower -1 or equal 0 */
#define CHECK_FOR_ZERO_OR_MINUS_LENGTH(length) \
    CHECK_FOR_ZERO_OR_MINUS_LENGTH_AND_CLEANUP(length, ((void)0))

/** See inlined comments.
 @param tree the tree to append this item to
 @param hfindex field index
 @param hfinfo header_field
 @param free_block a code block to call to free resources if this returns
 @return the header field matching 'hfinfo' */
#define TRY_TO_FAKE_THIS_ITEM_OR_FREE(tree, hfindex, hfinfo, free_block) \
    /* If the tree is not visible and this item is not referenced    \
       we don't have to do much work at all but we should still    \
       return a node so that referenced field items below this node    \
       (think proto_item_add_subtree()) will still have somewhere    \
       to attach to or else filtering will not work (they would be    \
       ignored since tree would be NULL).                \
       DON'T try to fake a node where PTREE_FINFO(tree) is NULL    \
       since dissectors that want to do proto_item_set_len() or    \
       other operations that dereference this would crash.        \
       DON'T try to fake a node where PTREE_FINFO(tree) is visible    \
       because that means we can change its length or repr, and we    \
       don't want to do so with calls intended for this faked new    \
       item, so this item needs a new (hidden) child node.        \
       (PROTO_ITEM_IS_HIDDEN(tree) checks both conditions.)        \
       We fake FT_PROTOCOL unless some clients have requested us    \
       not to do so.                        \
    */                                \
    PTREE_DATA(tree)->count++;                    \
    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);            \
    if (PTREE_DATA(tree)->count > max_tree_items) {    \
        free_block;                        \
        /* Let the exception handler add items to the tree */    \
        PTREE_DATA(tree)->count = 0;                \
    }                                \
    if (!(PTREE_DATA(tree)->visible)) {                \
        if (PROTO_ITEM_IS_HIDDEN(tree)) {            \
            if ((hfinfo->ref_type != HF_REF_TYPE_DIRECT)    \
                && (hfinfo->ref_type != HF_REF_TYPE_PRINT)    \
                && (hfinfo->type != FT_PROTOCOL ||        \
                PTREE_DATA(tree)->fake_protocols)) {    \
                free_block;                \
                /* just return tree back to the caller */\
                return tree;                \
            }                        \
        }                            \
    }

/** See inlined comments.
 @param tree the tree to append this item to
 @param hfindex field index
 @param hfinfo header_field
 @return the header field matching 'hfinfo' */
#define TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo) \
    TRY_TO_FAKE_THIS_ITEM_OR_FREE(tree, hfindex, hfinfo, ((void)0))


/** See inlined comments.
 @param pi the created protocol item we're about to return */
#define TRY_TO_FAKE_THIS_REPR(pi)    \
    if (!(PTREE_DATA(pi)->visible) && \
          PROTO_ITEM_IS_HIDDEN(pi)) { \
        /* If the tree (GUI) or item isn't visible it's pointless for \
         * us to generate the protocol item's string representation */ \
        return pi; \
    }
/* Same as above but returning void */
#define TRY_TO_FAKE_THIS_REPR_VOID(pi)    \
    if (!pi)            \
        return;            \
    if (!(PTREE_DATA(pi)->visible) && \
          PROTO_ITEM_IS_HIDDEN(pi)) { \
        /* If the tree (GUI) or item isn't visible it's pointless for \
         * us to generate the protocol item's string representation */ \
        return; \
    }
/* Similar to above, but allows a NULL tree */
#define TRY_TO_FAKE_THIS_REPR_NESTED(pi)    \
    if ((pi == NULL) || (!(PTREE_DATA(pi)->visible) && \
        PROTO_ITEM_IS_HIDDEN(pi))) { \
        /* If the tree (GUI) or item isn't visible it's pointless for \
         * us to generate the protocol item's string representation */ \
        return pi; \
    }

#ifdef ENABLE_CHECK_FILTER
#define CHECK_HF_VALUE(type, spec, start_values) \
{ \
    const type *current; \
    int n, m; \
    current = start_values; \
    for (n=0; current; n++, current++) { \
        /* Drop out if we reached the end. */ \
        if ((current->value == 0) && (current->strptr == NULL)) { \
            break; \
        } \
        /* Check value against all previous */ \
        for (m=0; m < n; m++) { \
            /* There are lots of duplicates with the same string, \
               so only report if different... */ \
            if ((start_values[m].value == current->value) && \
                (strcmp(start_values[m].strptr, current->strptr) != 0)) { \
                printf("Field '%s' (%s) has a conflicting entry in its" \
                      " value_string: %" spec " is at indices %u (%s) and %u (%s)", \
                      hfinfo->name, hfinfo->abbrev, \
                      current->value, m, start_values[m].strptr, n, current->strptr); \
            } \
        } \
    } \
}
#endif

/* The longest NUMBER-like field label we have is for BASE_OUI, which
 * can have up to 64 bytes for the manufacturer name if resolved plus
 * 11 bytes for the "XX:XX:XX ()" part = 75 octets.
 */
#define NUMBER_LABEL_LENGTH 80

static const char *hf_try_val_to_str(uint32_t value, const hf_info *hfinfo);
static const char *hf_try_val64_to_str(uint64_t value, const hf_info *hfinfo);
static const char *hf_try_val_to_str_const(uint32_t value, const hf_info *hfinfo, const char *unknown_str);
static const char *hf_try_val64_to_str_const(uint64_t value, const hf_info *hfinfo, const char *unknown_str);
static int hfinfo_bitoffset(const hf_info *hfinfo);
static int hfinfo_mask_bitwidth(const hf_info *hfinfo);
static int hfinfo_container_bitwidth(const hf_info *hfinfo);

#define label_concat(dst, pos, src) \
    label_strcpy(dst, ITEM_LABEL_LENGTH, pos, src, 0)

static void mark_truncated(char *label_str, size_t name_pos, const size_t size);
static void label_mark_truncated(char *label_str, size_t name_pos);
#define LABEL_MARK_TRUNCATED_START(label_str) label_mark_truncated(label_str, 0)

static void fill_label_boolean(const field_info *fi, char *label_str);
static void fill_label_bitfield_char(const field_info *fi, char *label_str);
static void fill_label_bitfield(const field_info *fi, char *label_str, bool is_signed);
static void fill_label_bitfield64(const field_info *fi, char *label_str, bool is_signed);
static void fill_label_char(const field_info *fi, char *label_str);
static void fill_label_number(const field_info *fi, char *label_str, bool is_signed);
static void fill_label_number64(const field_info *fi, char *label_str, bool is_signed);

static size_t fill_display_label_float(const field_info *fi, char *label_str);
static void fill_label_float(const field_info *fi, char *label_str);

static const char *hfinfo_number_value_format_display(const hf_info *hfinfo, int display, char buf[NUMBER_LABEL_LENGTH], uint32_t value);
static const char *hfinfo_number_value_format_display64(const hf_info *hfinfo, int display, char buf[NUMBER_LABEL_LENGTH], uint64_t value);
static const char *hfinfo_char_vals_format(const hf_info *hfinfo, char buf[32], uint32_t value);
static const char *hfinfo_number_vals_format(const hf_info *hfinfo, char buf[NUMBER_LABEL_LENGTH], uint32_t value);
static const char *hfinfo_number_vals_format64(const hf_info *hfinfo, char buf[NUMBER_LABEL_LENGTH], uint64_t value);
static const char *hfinfo_number_value_format(const hf_info *hfinfo, char buf[NUMBER_LABEL_LENGTH], uint32_t value);
static const char *hfinfo_number_value_format64(const hf_info *hfinfo, char buf[NUMBER_LABEL_LENGTH], uint64_t value);
static const char *hfinfo_char_value_format(const hf_info *hfinfo, char buf[32], uint32_t value);
static const char *hfinfo_numeric_value_format(const hf_info *hfinfo, char buf[NUMBER_LABEL_LENGTH], uint32_t value);
static const char *hfinfo_numeric_value_format64(const hf_info *hfinfo, char buf[NUMBER_LABEL_LENGTH], uint64_t value);

static void proto_cleanup_base(void);

static proto_item *proto_tree_add_node(proto_tree *tree, field_info *fi);

static void get_hfi_length(hf_info *hfinfo, tvbuff_t *tvb, const int start, int *length,
        int *item_length, const unsigned encoding);

static int get_full_length(hf_info *hfinfo, tvbuff_t *tvb, const int start,
        int length, unsigned item_length, const int encoding);

static field_info *new_field_info(proto_tree *tree, hf_info *hfinfo, tvbuff_t *tvb,
           const int start, const int item_length);

static proto_item *proto_tree_add_pi(proto_tree *tree, hf_info *hfinfo, tvbuff_t *tvb,
          int start, int *length);

static void proto_tree_set_representation_value(proto_item *pi, const char *format, va_list ap);
static void proto_tree_set_representation(proto_item *pi, const char *format, va_list ap);

static void proto_tree_set_protocol_tvb(field_info *fi, tvbuff_t *tvb, const char* field_data, int length);

static void proto_tree_set_bytes(field_info *fi, const uint8_t* start_ptr, int length);
static void proto_tree_set_bytes_tvb(field_info *fi, tvbuff_t *tvb, int offset, int length);
static void proto_tree_set_bytes_gbytearray(field_info *fi, const GByteArray *value);

static void proto_tree_set_string(field_info *fi, const char* value);

//static void
//proto_tree_set_time(field_info *fi, const nstime_t *value_ptr);
//static void
//proto_tree_set_ax25(field_info *fi, const uint8_t* value);
//static void
//proto_tree_set_ax25_tvb(field_info *fi, tvbuff_t *tvb, int start);
//static void
//proto_tree_set_vines(field_info *fi, const uint8_t* value);
//static void
//proto_tree_set_vines_tvb(field_info *fi, tvbuff_t *tvb, int start);
//static void
//proto_tree_set_ether(field_info *fi, const uint8_t* value);
//static void
//proto_tree_set_ether_tvb(field_info *fi, tvbuff_t *tvb, int start);
//static void
//proto_tree_set_ipxnet(field_info *fi, uint32_t value);
//static void
//proto_tree_set_ipv4(field_info *fi, ws_in4_addr value);
//static void
//proto_tree_set_ipv6(field_info *fi, const ws_in6_addr* value);
//static void
//proto_tree_set_ipv6_tvb(field_info *fi, tvbuff_t *tvb, int start, int length);
//static void
//proto_tree_set_fcwwn_tvb(field_info *fi, tvbuff_t *tvb, int start, int length);
//static void
//proto_tree_set_guid(field_info *fi, const e_guid_t *value_ptr);
//static void
//proto_tree_set_guid_tvb(field_info *fi, tvbuff_t *tvb, int start, const unsigned encoding);
//static void
//proto_tree_set_oid(field_info *fi, const uint8_t* value_ptr, int length);
//static void
//proto_tree_set_oid_tvb(field_info *fi, tvbuff_t *tvb, int start, int length);
//static void
//proto_tree_set_system_id(field_info *fi, const uint8_t* value_ptr, int length);
//static void
//proto_tree_set_system_id_tvb(field_info *fi, tvbuff_t *tvb, int start, int length);

static void proto_tree_set_boolean(field_info *fi, uint64_t value);
static void proto_tree_set_float(field_info *fi, float value);
static void proto_tree_set_double(field_info *fi, double value);
static void proto_tree_set_uint(field_info *fi, uint32_t value);
static void proto_tree_set_int(field_info *fi, int32_t value);
static void proto_tree_set_uint64(field_info *fi, uint64_t value);
static void proto_tree_set_int64(field_info *fi, int64_t value);

//static void
//proto_tree_set_eui64(field_info *fi, const uint64_t value);
//static void
//proto_tree_set_eui64_tvb(field_info *fi, tvbuff_t *tvb, int start, const unsigned encoding);

/* Handle type length mismatch (now filterable) expert info */
//static int proto_type_length_mismatch;
//static expert_field ei_type_length_mismatch_error;
//static expert_field ei_type_length_mismatch_warn;
//static void register_type_length_mismatch(void);

/* Handle byte array string decoding errors with expert info */
//static int proto_byte_array_string_decoding_error;
//static expert_field ei_byte_array_string_decoding_failed_error;
//static void register_byte_array_string_decodinws_error(void);

/* Handle date and time string decoding errors with expert info */
//static int proto_date_time_string_decoding_error;
//static expert_field ei_date_time_string_decoding_failed_error;
//static void register_date_time_string_decodinws_error(void);

/* Handle string errors expert info */
//static int proto_string_errors;
//static expert_field ei_string_trailing_characters;
//static void register_string_errors(void);

static int proto_register_field_init(hf_info *hfinfo, const int parent);

/* special-case header field used within proto.c */
static hf_info hfi_text_only =
    { "Text item",    "text", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL };
int hf_text_only;

/* Structure for information about a protocol */
struct _protocol {
    const char *name;               /* long description */
    const char *short_name;         /* short description */
    const char *filter_name;        /* name of this protocol in filters */
    GPtrArray  *fields;             /* fields for this protocol */
    int         proto_id;           /* field ID for this protocol */
    bool        is_enabled;         /* true if protocol is enabled */
    bool        enabled_by_default; /* true if protocol is enabled by default */
    bool        can_toggle;         /* true if is_enabled can be changed */
    int         parent_proto_id;    /* Used to identify "pino"s (Protocol In Name Only).
                                       For dissectors that need a protocol name so they
                                       can be added to a dissector table, but use the
                                       parent_proto_id for things like enable/disable */
};

/* List of all protocols */
static GList *protocols;

/* Structure stored for deregistered g_slice */
struct g_slice_data {
    size_t   block_size;
    void    *mem_block;
};

/* Deregistered fields */
static GPtrArray *deregistered_fields;
static GPtrArray *deregistered_data;
static GPtrArray *deregistered_slice;

/* indexed by prefix, contains initializers */
static GHashTable* prefixes;

/* Contains information about a field when a dissector calls
 * proto_tree_add_item.  */
#define FIELD_INFO_NEW(fi)  fi = wmem_new(field_info)
#define FIELD_INFO_FREE(fi) wmem_free(fi)

/* Contains the space for proto_nodes. */
#define PROTO_NODE_INIT(node)            \
    node->first_child = NULL;        \
    node->last_child = NULL;        \
    node->next = NULL;

#define PROTO_NODE_FREE(node)            \
    wmem_free(node)

/* String space for protocol and field items for the GUI */
#define ITEM_LABEL_NEW(il)            \
    il = wmem_new(item_label_t);
#define ITEM_LABEL_FREE(il)            \
    wmem_free(il);

#define PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo)                        \
    if((hfindex == 0 || (unsigned)hfindex > gpa_hfinfo.len)/* && wireshark_abort_on_dissector_bug*/)    \
        printf("Unregistered hf! index=%d", hfindex);                    \
    DISSECTOR_ASSERT_HINT(hfindex > 0 && (unsigned)hfindex < gpa_hfinfo.len, "Unregistered hf!");    \
    DISSECTOR_ASSERT_HINT(gpa_hfinfo.hfi[hfindex] != NULL, "Unregistered hf!");    \
    hfinfo = gpa_hfinfo.hfi[hfindex];

/* List which stores protocols and fields that have been registered */
typedef struct _gpa_hfinfo_t {
    uint32_t            len;
    uint32_t            allocated_len;
    hf_info **hfi;
} gpa_hfinfo_t;

static gpa_hfinfo_t gpa_hfinfo;

/* Hash table of abbreviations and IDs */
static GHashTable *gpa_name_map;
static hf_info *same_name_hfinfo;

/* Hash table protocol aliases. const char * -> const char * */
static GHashTable *gpa_protocol_aliases;

/*
 * We're called repeatedly with the same field name when sorting a column.
 * Cache our last gpa_name_map hit for faster lookups.
 */
static char *last_field_name;
static hf_info *last_hfinfo;

static void save_same_name_hfinfo(void *data)
{
    same_name_hfinfo = (hf_info*)data;
}

/* Points to the first element of an array of bits, indexed by
   a subtree item type; that array element is true if subtrees of
   an item of that type are to be expanded. */
static uint32_t *tree_is_expanded;

/* Number of elements in that array. The entry with index 0 is not used. */
int        num_tree_types = 1;

/* Name hashtables for fast detection of duplicate names */
static GHashTable* proto_names;
static GHashTable* proto_short_names;
static GHashTable* proto_filter_names;

static const char *reserved_filter_names[] = {
    /* Display filter keywords. */
    "eq",
    "ne",
    "all_eq",
    "any_eq",
    "all_ne",
    "any_ne",
    "gt",
    "ge",
    "lt",
    "le",
    "bitand",
    "bitwise_and",
    "contains",
    "matches",
    "not",
    "and",
    "or",
    "xor",
    "in",
    "any",
    "all",
    "true",
    "false",
    "nan",
    "inf",
    NULL
};

static GHashTable *proto_reserved_filter_names;

static int proto_compare_name(const void *p1_arg, const void *p2_arg)
{
    const protocol_t *p1 = (const protocol_t *)p1_arg;
    const protocol_t *p2 = (const protocol_t *)p2_arg;

    return g_ascii_strcasecmp(p1->short_name, p2->short_name);
}

static GSList *dissector_plugins;

#ifdef HAVE_PLUGINS
void proto_register_plugin(const proto_plugin *plug)
{
    dissector_plugins = g_slist_prepend(dissector_plugins, (proto_plugin *)plug);
}
#else /* HAVE_PLUGINS */
//void proto_register_plugin(const proto_plugin *plug)
//{
//    printf("proto_register_plugin: built without support for binary plugins");
//}
#endif /* HAVE_PLUGINS */

//static void
//call_plugin_register_protoinfo(void *data, void *user_data)
//{
//    proto_plugin *plug = (proto_plugin *)data;

//    if (plug->register_protoinfo) {
//        plug->register_protoinfo();
//    }
//}

//static void call_plugin_register_handoff(void *data, void *user_data)
//{
//    proto_plugin *plug = (proto_plugin *)data;

//    if (plug->register_handoff) {
//        plug->register_handoff();
//    }
//}

/* initialize data structures and register protocols and fields */
void proto_init(GSList *register_all_plugin_protocols_list,
       GSList *register_all_plugin_handoffs_list)
{
    proto_cleanup_base();

    proto_names        = g_hash_table_new(g_str_hash, g_str_equal);
    proto_short_names  = g_hash_table_new(g_str_hash, g_str_equal);
    proto_filter_names = g_hash_table_new(g_str_hash, g_str_equal);

    proto_reserved_filter_names = g_hash_table_new(g_str_hash, g_str_equal);
    for (const char **ptr = reserved_filter_names; *ptr != NULL; ptr++) {
        /* GHashTable has no key destructor so the cast is safe. */
        g_hash_table_add(proto_reserved_filter_names, *(char **)ptr);
    }

    gpa_hfinfo.len           = 0;
    gpa_hfinfo.allocated_len = 0;
    gpa_hfinfo.hfi           = NULL;
    gpa_name_map             = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, save_same_name_hfinfo);
    gpa_protocol_aliases     = g_hash_table_new(g_str_hash, g_str_equal);
    deregistered_fields      = g_ptr_array_new();
    deregistered_data        = g_ptr_array_new();
    deregistered_slice       = g_ptr_array_new();

    /* Initialize the ftype subsystem */
    ftypes_initialize();

    /* Register one special-case FT_TEXT_ONLY field for use when
       converting wireshark to new-style proto_tree. These fields
       are merely strings on the GUI tree; they are not filterable */
    hf_text_only = proto_register_field_init(&hfi_text_only, -1);

    /* We've assigned all the subtree type values; allocate the array
       for them, and zero it out. */
    tree_is_expanded = calloc((num_tree_types/32)+1, sizeof(uint32_t));
}

static void proto_cleanup_base(void)
{
    protocol_t *protocol;
    hf_info *hfinfo;

    /* Free the abbrev/ID hash table */
    if (gpa_name_map) {
        g_hash_table_destroy(gpa_name_map);
        gpa_name_map = NULL;
    }
    if (gpa_protocol_aliases) {
        g_hash_table_destroy(gpa_protocol_aliases);
        gpa_protocol_aliases = NULL;
    }
    g_free(last_field_name);
    last_field_name = NULL;

    while (protocols) {
        protocol = (protocol_t *)protocols->data;
        PROTO_REGISTRAR_GET_NTH(protocol->proto_id, hfinfo);
        DISSECTOR_ASSERT(protocol->proto_id == hfinfo->id);

        g_slice_free(hf_info, hfinfo);
        if (protocol->parent_proto_id != -1) {
            // pino protocol
            DISSECTOR_ASSERT(protocol->fields == NULL); //helpers should not have any registered fields
        } else {
            if (protocol->fields) {
                g_ptr_array_free(protocol->fields, true);
            }
        }
        protocols = g_list_remove(protocols, protocol);
        g_free(protocol);
    }

    if (proto_names) {
        g_hash_table_destroy(proto_names);
        proto_names = NULL;
    }

    if (proto_short_names) {
        g_hash_table_destroy(proto_short_names);
        proto_short_names = NULL;
    }

    if (proto_filter_names) {
        g_hash_table_destroy(proto_filter_names);
        proto_filter_names = NULL;
    }

    if (proto_reserved_filter_names) {
        g_hash_table_destroy(proto_reserved_filter_names);
        proto_reserved_filter_names = NULL;
    }

    if (gpa_hfinfo.allocated_len) {
        gpa_hfinfo.len           = 0;
        gpa_hfinfo.allocated_len = 0;
        g_free(gpa_hfinfo.hfi);
        gpa_hfinfo.hfi           = NULL;
    }

    if (deregistered_fields) {
        g_ptr_array_free(deregistered_fields, true);
        deregistered_fields = NULL;
    }

    if (deregistered_data) {
        g_ptr_array_free(deregistered_data, true);
        deregistered_data = NULL;
    }

    if (deregistered_slice) {
        g_ptr_array_free(deregistered_slice, true);
        deregistered_slice = NULL;
    }

    g_free(tree_is_expanded);
    tree_is_expanded = NULL;

    if (prefixes)
        g_hash_table_destroy(prefixes);
}

void proto_cleanup(void)
{
    proto_free_deregistered_fields();
    proto_cleanup_base();

    g_slist_free(dissector_plugins);
    dissector_plugins = NULL;
}

/* Assume dissector set only its protocol fields.
   This function is called by dissectors and allows the speeding up of filtering
   in wireshark; if this function returns false it is safe to reset tree to NULL
   and thus skip calling most of the expensive proto_tree_add_...()
   functions.
   If the tree is visible we implicitly assume the field is referenced.
*/
bool proto_field_is_referenced(proto_tree *tree, int proto_id)
{
    register hf_info *hfinfo;


    if (!tree)
        return false;

    if (PTREE_DATA(tree)->visible)
        return true;

    PROTO_REGISTRAR_GET_NTH(proto_id, hfinfo);
    if (hfinfo->ref_type != HF_REF_TYPE_NONE)
        return true;

    if (hfinfo->type == FT_PROTOCOL && !PTREE_DATA(tree)->fake_protocols)
        return true;

    return false;
}


/* Finds a record in the hfinfo array by id. */
hf_info *proto_registrar_get_nth(unsigned hfindex)
{
    register hf_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);
    return hfinfo;
}


/*    Prefix initialization
 *      this allows for a dissector to register a display filter name prefix
 *      so that it can delay the initialization of the hf array as long as
 *      possible.
 */

/* compute a hash for the part before the dot of a display filter */
static unsigned prefix_hash (const void *key) {
    /* end the string at the dot and compute its hash */
    char* copy = g_strdup((const char *)key);
    char* c    = copy;
    unsigned tmp;

    for (; *c; c++) {
        if (*c == '.') {
            *c = 0;
            break;
        }
    }

    tmp = g_str_hash(copy);
    g_free(copy);
    return tmp;
}

/* are both strings equal up to the end or the dot? */
static gboolean prefix_equal (const void *ap, const void *bp) {
    const char* a = (const char *)ap;
    const char* b = (const char *)bp;

    do {
        char ac = *a++;
        char bc = *b++;

        if ( (ac == '.' || ac == '\0') &&   (bc == '.' || bc == '\0') ) return TRUE;

        if ( (ac == '.' || ac == '\0') && ! (bc == '.' || bc == '\0') ) return FALSE;
        if ( (bc == '.' || bc == '\0') && ! (ac == '.' || ac == '\0') ) return FALSE;

        if (ac != bc) return FALSE;
    } while (1);

    return FALSE;
}

/* Register a new prefix for "delayed" initialization of field arrays */
void proto_register_prefix(const char *prefix, prefix_initializer_t pi ) {
    if (! prefixes ) {
        prefixes = g_hash_table_new(prefix_hash, prefix_equal);
    }

    g_hash_table_insert(prefixes, (void *)prefix, (void *)pi);
}

/* helper to call all prefix initializers */
static gboolean initialize_prefix(void *k, void *v, void *u) {
    ((prefix_initializer_t)v)((const char *)k);
    return TRUE;
}

/** Initialize every remaining uninitialized prefix. */
void proto_initialize_all_prefixes(void)
{
    g_hash_table_foreach_remove(prefixes, initialize_prefix, NULL);
}

/* Finds a record in the hfinfo array by name.
 * If it fails to find it in the already registered fields,
 * it tries to find and call an initializer in the prefixes
 * table and if so it looks again.
 */

hf_info *proto_registrar_get_byname(const char *field_name)
{
    hf_info    *hfinfo;
    prefix_initializer_t  pi;

    if (!field_name)
        return NULL;

    if (g_strcmp0(field_name, last_field_name) == 0) {
        return last_hfinfo;
    }

    hfinfo = (hf_info *)g_hash_table_lookup(gpa_name_map, field_name);

    if (hfinfo) {
        g_free(last_field_name);
        last_field_name = g_strdup(field_name);
        last_hfinfo = hfinfo;
        return hfinfo;
    }

    if (!prefixes)
        return NULL;

    if ((pi = (prefix_initializer_t)g_hash_table_lookup(prefixes, field_name) ) != NULL) {
        pi(field_name);
        g_hash_table_remove(prefixes, field_name);
    } else {
        return NULL;
    }

    hfinfo = (hf_info *)g_hash_table_lookup(gpa_name_map, field_name);

    if (hfinfo) {
        g_free(last_field_name);
        last_field_name = g_strdup(field_name);
        last_hfinfo = hfinfo;
    }
    return hfinfo;
}



#include "proto_tree_i.c"




static void check_protocol_filter_name_or_fail(const char *filter_name)
{
    /* Require at least two characters. */
    if (filter_name[0] == '\0' || filter_name[1] == '\0') {
        REPORT_DISSECTOR_BUG("Protocol filter name \"%s\" cannot have length less than two.", filter_name);
    }

    if (proto_check_field_name(filter_name) != '\0') {
        REPORT_DISSECTOR_BUG("Protocol filter name \"%s\" has one or more invalid characters."
            " Allowed are letters, digits, '-', '_' and non-repeating '.'."
            " This might be caused by an inappropriate plugin or a development error.", filter_name);
    }

    /* Check that it doesn't match some very common numeric forms. */
    if (filter_name[0] == '0' &&
                (filter_name[1] == 'x' || filter_name[1] == 'X' ||
                filter_name[1] == 'b' || filter_name[1] == 'B')) {
        REPORT_DISSECTOR_BUG("Protocol filter name \"%s\" cannot start with \"%c%c\".",
                        filter_name, filter_name[0], filter_name[1]);
    }

    /* Names starting with a digit must not contain a minus sign (currently not checked at runtime). */

    /* Check that it contains at least one letter. */
    bool have_letter = false;
    for (const char *s = filter_name; *s != '\0'; s++) {
        if (g_ascii_isalpha(*s)) {
            have_letter = true;
            break;
        }
    }
    if (!have_letter) {
        REPORT_DISSECTOR_BUG("Protocol filter name \"%s\" must contain at least one letter a-z.",
                        filter_name);
    }

    /* Check for reserved keywords. */
    if (g_hash_table_contains(proto_reserved_filter_names, filter_name)) {
        REPORT_DISSECTOR_BUG("Protocol filter name \"%s\" is invalid because it is a reserved keyword."
            " This might be caused by an inappropriate plugin or a development error.", filter_name);
    }
}

int proto_register_protocol(const char *name, const char *short_name,
            const char *filter_name)
{
    protocol_t *protocol;
    hf_info *hfinfo;

    /*
     * Make sure there's not already a protocol with any of those
     * names.  Crash if there is, as that's an error in the code
     * or an inappropriate plugin.
     * This situation has to be fixed to not register more than one
     * protocol with the same name.
     */

    if (g_hash_table_lookup(proto_names, name)) {
        /* ws_error will terminate the program */
        REPORT_DISSECTOR_BUG("Duplicate protocol name \"%s\"!"
            " This might be caused by an inappropriate plugin or a development error.", name);
    }

    if (g_hash_table_lookup(proto_short_names, short_name)) {
        REPORT_DISSECTOR_BUG("Duplicate protocol short_name \"%s\"!"
            " This might be caused by an inappropriate plugin or a development error.", short_name);
    }

    check_protocol_filter_name_or_fail(filter_name);

    if (g_hash_table_lookup(proto_filter_names, filter_name)) {
        REPORT_DISSECTOR_BUG("Duplicate protocol filter_name \"%s\"!"
            " This might be caused by an inappropriate plugin or a development error.", filter_name);
    }

    /*
     * Add this protocol to the list of known protocols;
     * the list is sorted by protocol short name.
     */
    protocol = g_new(protocol_t, 1);
    protocol->name = name;
    protocol->short_name = short_name;
    protocol->filter_name = filter_name;
    protocol->fields = NULL; /* Delegate until actually needed */
    protocol->is_enabled = true; /* protocol is enabled by default */
    protocol->enabled_by_default = true; /* see previous comment */
    protocol->can_toggle = true;
    protocol->parent_proto_id = -1;

    /* List will be sorted later by name, when all protocols completed registering */
    protocols = g_list_prepend(protocols, protocol);
    g_hash_table_insert(proto_names, (void *)name, protocol);
    g_hash_table_insert(proto_filter_names, (void *)filter_name, protocol);
    g_hash_table_insert(proto_short_names, (void *)short_name, protocol);

    /* Here we allocate a new header_field_info struct */
    hfinfo = g_slice_new(hf_info);
    hfinfo->name = name;
    hfinfo->abbrev = filter_name;
    hfinfo->type = FT_PROTOCOL;
    hfinfo->display = BASE_NONE;
    hfinfo->strings = protocol;
    hfinfo->bitmask = 0;
    hfinfo->ref_type = HF_REF_TYPE_NONE;
    hfinfo->blurb = NULL;
    hfinfo->parent = -1; /* This field differentiates protos and fields */

    protocol->proto_id = proto_register_field_init(hfinfo, hfinfo->parent);

    return protocol->proto_id;
}

bool proto_deregister_protocol(const char *short_name)
{
    protocol_t *protocol;
    hf_info *hfinfo;
    int proto_id;
    unsigned i;

    proto_id = proto_get_id_by_short_name(short_name);
    protocol = find_protocol_by_id(proto_id);
    if (protocol == NULL)
        return false;

    g_hash_table_remove(proto_names, protocol->name);
    g_hash_table_remove(proto_short_names, (void *)short_name);
    g_hash_table_remove(proto_filter_names, (void *)protocol->filter_name);

    if (protocol->fields) {
        for (i = 0; i < protocol->fields->len; i++) {
            hfinfo = (hf_info *)g_ptr_array_index(protocol->fields, i);
            g_ptr_array_add(deregistered_fields, gpa_hfinfo.hfi[hfinfo->id]);
        }
        g_ptr_array_free(protocol->fields, true);
        protocol->fields = NULL;
    }

    /* Remove this protocol from the list of known protocols */
    protocols = g_list_remove(protocols, protocol);

    g_ptr_array_add(deregistered_fields, gpa_hfinfo.hfi[proto_id]);
    g_hash_table_steal(gpa_name_map, protocol->filter_name);

    g_free(last_field_name);
    last_field_name = NULL;

    return true;
}

/*
 * Routines to use to iterate over the protocols.
 * The argument passed to the iterator routines is an opaque cookie to
 * their callers; it's the GList pointer for the current element in
 * the list.
 * The ID of the protocol is returned, or -1 if there is no protocol.
 */
int proto_get_first_protocol(void **cookie)
{
    protocol_t *protocol;

    if (protocols == NULL)
        return -1;
    *cookie = protocols;
    protocol = (protocol_t *)protocols->data;

    return protocol->proto_id;
}

int proto_get_data_protocol(void *cookie)
{
    GList *list_item = (GList *)cookie;

    protocol_t *protocol = (protocol_t *)list_item->data;

    return protocol->proto_id;
}

int proto_get_next_protocol(void **cookie)
{
    GList      *list_item = (GList *)*cookie;
    protocol_t *protocol;

    list_item = g_list_next(list_item);
    if (list_item == NULL)
        return -1;

    *cookie = list_item;
    protocol = (protocol_t *)list_item->data;

    return protocol->proto_id;
}

protocol_t *find_protocol_by_id(const int proto_id)
{
    hf_info *hfinfo;

    if (proto_id <= 0)
        return NULL;

    PROTO_REGISTRAR_GET_NTH(proto_id, hfinfo);
    if (hfinfo->type != FT_PROTOCOL) {
        DISSECTOR_ASSERT(hfinfo->display & BASE_PROTOCOL_INFO);
    }
    return (protocol_t *)hfinfo->strings;
}

int proto_get_id(const protocol_t *protocol)
{
    return protocol->proto_id;
}

bool proto_name_already_registered(const char *name)
{
    DISSECTOR_ASSERT_HINT(name, "No name present");

    if (g_hash_table_lookup(proto_names, name) != NULL)
        return true;
    return false;
}

int proto_get_id_by_filter_name(const char *filter_name)
{
    const protocol_t *protocol = NULL;

    DISSECTOR_ASSERT_HINT(filter_name, "No filter name present");

    protocol = (const protocol_t *)g_hash_table_lookup(proto_filter_names, filter_name);

    if (protocol == NULL)
        return -1;

    return protocol->proto_id;
}

int proto_get_id_by_short_name(const char *short_name)
{
    const protocol_t *protocol = NULL;

    DISSECTOR_ASSERT_HINT(short_name, "No short name present");

    protocol = (const protocol_t *)g_hash_table_lookup(proto_short_names, short_name);

    if (protocol == NULL)
        return -1;

    return protocol->proto_id;
}

const char *proto_get_protocol_name(const int proto_id)
{
    protocol_t *protocol;

    protocol = find_protocol_by_id(proto_id);

    if (protocol == NULL)
        return NULL;

    return protocol->name;
}

const char *proto_get_protocol_short_name(const protocol_t *protocol)
{
    if (protocol == NULL)
        return "(none)";

    return protocol->short_name;
}

const char *proto_get_protocol_long_name(const protocol_t *protocol)
{
    if (protocol == NULL)
        return "(none)";

    return protocol->name;
}

const char *proto_get_protocol_filter_name(const int proto_id)
{
    protocol_t *protocol;

    protocol = find_protocol_by_id(proto_id);
    if (protocol == NULL)
        return "(none)";

    return protocol->filter_name;
}

bool proto_is_pino(const protocol_t *protocol)
{
    return (protocol->parent_proto_id != -1);
}

bool proto_is_protocol_enabled(const protocol_t *protocol)
{
    if (protocol == NULL)
        return false;

    //parent protocol determines enable/disable for helper dissectors
    if (proto_is_pino(protocol))
        return proto_is_protocol_enabled(find_protocol_by_id(protocol->parent_proto_id));

    return protocol->is_enabled;
}

bool proto_can_toggle_protocol(const int proto_id)
{
    protocol_t *protocol;

    protocol = find_protocol_by_id(proto_id);
    //parent protocol determines toggling for helper dissectors
    if (proto_is_pino(protocol))
        return proto_can_toggle_protocol(protocol->parent_proto_id);

    return protocol->can_toggle;
}

void proto_set_decoding(const int proto_id, const bool enabled)
{
    protocol_t *protocol;

    protocol = find_protocol_by_id(proto_id);
    DISSECTOR_ASSERT(proto_is_pino(protocol) == false);
    protocol->is_enabled = enabled;
}

void proto_disable_all(void)
{
    /* This doesn't explicitly disable heuristic protocols,
     * but the heuristic doesn't get called if the parent
     * protocol isn't enabled.
     */
    protocol_t *protocol;
    GList      *list_item = protocols;

    if (protocols == NULL)
        return;

    while (list_item) {
        protocol = (protocol_t *)list_item->data;
        if (protocol->can_toggle) {
            protocol->is_enabled = false;
        }
        list_item = g_list_next(list_item);
    }
}

void proto_reenable_all(void)
{
    protocol_t *protocol;
    GList      *list_item = protocols;

    if (protocols == NULL)
        return;

    while (list_item) {
        protocol = (protocol_t *)list_item->data;
        if (protocol->can_toggle) {
            protocol->is_enabled = protocol->enabled_by_default;
        }
        list_item = g_list_next(list_item);
    }
}

static int proto_register_field_common(protocol_t *proto, hf_info *hfi, const int parent)
{
    if (proto != NULL) {
        g_ptr_array_add(proto->fields, hfi);
    }

    return proto_register_field_init(hfi, parent);
}

/* for use with static arrays only, since we don't allocate our own copies
of the header_field_info struct contained within the hf_register_info struct */
void proto_register_field_array(const int parent, hf_register_info *hf, const int num_records)
{
    hf_register_info *ptr = hf;
    protocol_t     *proto;
    int          i;

    proto = find_protocol_by_id(parent);

    if (proto->fields == NULL) {
        proto->fields = g_ptr_array_sized_new(num_records);
    }

    for (i = 0; i < num_records; i++, ptr++) {
        /*
         * Make sure we haven't registered this yet.
         * Most fields have variables associated with them
         * that are initialized to -1; some have array elements,
         * or possibly uninitialized variables, so we also allow
         * 0 (which is unlikely to be the field ID we get back
         * from "proto_register_field_init()").
         */
        if (*ptr->p_id != -1 && *ptr->p_id != 0) {
            REPORT_DISSECTOR_BUG(
                "Duplicate field detected in call to proto_register_field_array: %s is already registered",
                ptr->hfinfo.abbrev);
            return;
        }

        *ptr->p_id = proto_register_field_common(proto, &ptr->hfinfo, parent);
    }
}

void proto_add_deregistered_data (void *data)
{
    g_ptr_array_add(deregistered_data, data);
}

void proto_free_field_strings (ftenum_t field_type, unsigned int field_display, const void *field_strings)
{
    if (field_strings == NULL) {
        return;
    }

    switch (field_type) {
        case FT_FRAMENUM:
            /* This is just an integer represented as a pointer */
            break;
        case FT_PROTOCOL: {
            protocol_t *protocol = (protocol_t *)field_strings;
            g_free((char *)protocol->short_name);
            break;
        }
        case FT_BOOLEAN: {
            true_false_string *tf = (true_false_string *)field_strings;
            g_free((char *)tf->true_string);
            g_free((char *)tf->false_string);
            break;
        }
        case FT_UINT40:
        case FT_INT40:
        case FT_UINT48:
        case FT_INT48:
        case FT_UINT56:
        case FT_INT56:
        case FT_UINT64:
        case FT_INT64: {
            if (field_display & BASE_UNIT_STRING) {
                unit_name_string *unit = (unit_name_string *)field_strings;
                g_free((char *)unit->singular);
                g_free((char *)unit->plural);
            } else if (field_display & BASE_RANGE_STRING) {
                range_string *rs = (range_string *)field_strings;
                while (rs->strptr) {
                    g_free((char *)rs->strptr);
                    rs++;
                }
            } else if (field_display & BASE_EXT_STRING) {
                val64_string_ext *vse = (val64_string_ext *)field_strings;
                val64_string *vs = (val64_string *)vse->_vs_p;
                while (vs->strptr) {
                    g_free((char *)vs->strptr);
                    vs++;
                }
                val64_string_ext_free(vse);
                field_strings = NULL;
            } else if (field_display == BASE_CUSTOM) {
                /* this will be a pointer to a function, don't free that */
                field_strings = NULL;
            } else {
                val64_string *vs64 = (val64_string *)field_strings;
                while (vs64->strptr) {
                    g_free((char *)vs64->strptr);
                    vs64++;
                }
            }
            break;
        }
        case FT_CHAR:
        case FT_UINT8:
        case FT_INT8:
        case FT_UINT16:
        case FT_INT16:
        case FT_UINT24:
        case FT_INT24:
        case FT_UINT32:
        case FT_INT32:
        case FT_FLOAT:
        case FT_DOUBLE: {
            if (field_display & BASE_UNIT_STRING) {
                unit_name_string *unit = (unit_name_string *)field_strings;
                g_free((char *)unit->singular);
                g_free((char *)unit->plural);
            } else if (field_display & BASE_RANGE_STRING) {
                range_string *rs = (range_string *)field_strings;
                while (rs->strptr) {
                    g_free((char *)rs->strptr);
                    rs++;
                }
            } else if (field_display & BASE_EXT_STRING) {
                value_string_ext *vse = (value_string_ext *)field_strings;
                value_string *vs = (value_string *)vse->_vs_p;
                while (vs->strptr) {
                    g_free((char *)vs->strptr);
                    vs++;
                }
                value_string_ext_free(vse);
                field_strings = NULL;
            } else if (field_display == BASE_CUSTOM) {
                /* this will be a pointer to a function, don't free that */
                field_strings = NULL;
            } else {
                value_string *vs = (value_string *)field_strings;
                while (vs->strptr) {
                    g_free((char *)vs->strptr);
                    vs++;
                }
            }
            break;
        default:
            break;
        }
    }

    if (field_type != FT_FRAMENUM) {
        g_free((void *)field_strings);
    }
}

static void free_deregistered_field (void *data, void *user_data)
{
    hf_info *hfi = (hf_info *) data;
    int hf_id = hfi->id;

    g_free((char *)hfi->name);
    g_free((char *)hfi->abbrev);
    g_free((char *)hfi->blurb);

    proto_free_field_strings(hfi->type, hfi->display, hfi->strings);

    if (hfi->parent == -1)
        g_slice_free(hf_info, hfi);

    gpa_hfinfo.hfi[hf_id] = NULL; /* Invalidate this hf_id / proto_id */
}

static void free_deregistered_data (void *data, void *user_data)
{
    g_free (data);
}

static void free_deregistered_slice (void *data, void *user_data)
{
    struct g_slice_data *slice_data = (struct g_slice_data *)data;

    g_slice_free1(slice_data->block_size, slice_data->mem_block);
    g_slice_free(struct g_slice_data, slice_data);
}

/* free deregistered fields and data */
void proto_free_deregistered_fields (void)
{
//    expert_free_deregistered_expertinfos();

    g_ptr_array_foreach(deregistered_fields, free_deregistered_field, NULL);
    g_ptr_array_free(deregistered_fields, true);
    deregistered_fields = g_ptr_array_new();

    g_ptr_array_foreach(deregistered_data, free_deregistered_data, NULL);
    g_ptr_array_free(deregistered_data, true);
    deregistered_data = g_ptr_array_new();

    g_ptr_array_foreach(deregistered_slice, free_deregistered_slice, NULL);
    g_ptr_array_free(deregistered_slice, true);
    deregistered_slice = g_ptr_array_new();
}

static const value_string hf_display[] = {
    { BASE_NONE,              "BASE_NONE"               },
    { BASE_DEC,              "BASE_DEC"               },
    { BASE_HEX,              "BASE_HEX"               },
    { BASE_OCT,              "BASE_OCT"               },
    { BASE_DEC_HEX,              "BASE_DEC_HEX"           },
    { BASE_HEX_DEC,              "BASE_HEX_DEC"           },
    { BASE_CUSTOM,              "BASE_CUSTOM"               },
    { BASE_NONE|BASE_RANGE_STRING,    "BASE_NONE|BASE_RANGE_STRING"       },
    { BASE_DEC|BASE_RANGE_STRING,     "BASE_DEC|BASE_RANGE_STRING"       },
    { BASE_HEX|BASE_RANGE_STRING,     "BASE_HEX|BASE_RANGE_STRING"       },
    { BASE_OCT|BASE_RANGE_STRING,     "BASE_OCT|BASE_RANGE_STRING"       },
    { BASE_DEC_HEX|BASE_RANGE_STRING, "BASE_DEC_HEX|BASE_RANGE_STRING" },
    { BASE_HEX_DEC|BASE_RANGE_STRING, "BASE_HEX_DEC|BASE_RANGE_STRING" },
    { BASE_CUSTOM|BASE_RANGE_STRING,  "BASE_CUSTOM|BASE_RANGE_STRING"  },
    { BASE_NONE|BASE_VAL64_STRING,    "BASE_NONE|BASE_VAL64_STRING"       },
    { BASE_DEC|BASE_VAL64_STRING,     "BASE_DEC|BASE_VAL64_STRING"       },
    { BASE_HEX|BASE_VAL64_STRING,     "BASE_HEX|BASE_VAL64_STRING"       },
    { BASE_OCT|BASE_VAL64_STRING,     "BASE_OCT|BASE_VAL64_STRING"       },
    { BASE_DEC_HEX|BASE_VAL64_STRING, "BASE_DEC_HEX|BASE_VAL64_STRING" },
    { BASE_HEX_DEC|BASE_VAL64_STRING, "BASE_HEX_DEC|BASE_VAL64_STRING" },
    { BASE_CUSTOM|BASE_VAL64_STRING,  "BASE_CUSTOM|BASE_VAL64_STRING"  },
    { ABSOLUTE_TIME_LOCAL,          "ABSOLUTE_TIME_LOCAL"           },
    { ABSOLUTE_TIME_UTC,          "ABSOLUTE_TIME_UTC"           },
    { ABSOLUTE_TIME_DOY_UTC,      "ABSOLUTE_TIME_DOY_UTC"       },
    { BASE_PT_UDP,              "BASE_PT_UDP"               },
    { BASE_PT_TCP,              "BASE_PT_TCP"               },
    { BASE_PT_DCCP,              "BASE_PT_DCCP"           },
    { BASE_PT_SCTP,              "BASE_PT_SCTP"           },
    { BASE_OUI,              "BASE_OUI"               },
    { 0,                  NULL } };

const char* proto_field_display_to_string(int field_display)
{
    return val_to_str_const(field_display, hf_display, "Unknown");
}

/* Types of port numbers Wireshark knows about. */
typedef enum {
    PT_NONE,            /* no port number */
    PT_SCTP,            /* SCTP */
    PT_TCP,             /* TCP */
    PT_UDP,             /* UDP */
    PT_DCCP,            /* DCCP */
    PT_IPX,             /* IPX sockets */
    PT_DDP,             /* DDP AppleTalk connection */
    PT_IDP,             /* XNS IDP sockets */
    PT_USB,             /* USB endpoint 0xffff means the host */
    PT_I2C,
    PT_IBQP,            /* Infiniband QP number */
    PT_BLUETOOTH,
    PT_IWARP_MPA,       /* iWarp MPA */
    PT_MCTP
} port_type;

static inline port_type display_to_port_type(field_display_e e)
{
    switch (e) {
    case BASE_PT_UDP:
        return PT_UDP;
    case BASE_PT_TCP:
        return PT_TCP;
    case BASE_PT_DCCP:
        return PT_DCCP;
    case BASE_PT_SCTP:
        return PT_SCTP;
    default:
        break;
    }
    return PT_NONE;
}

#define PRE_ALLOC_EXPERT_FIELDS_MEM 5000
#define PROTO_PRE_ALLOC_HF_FIELDS_MEM (300000+PRE_ALLOC_EXPERT_FIELDS_MEM)
static int proto_register_field_init(hf_info *hfinfo, const int parent)
{
    hfinfo->parent         = parent;
    hfinfo->same_name_next = NULL;
    hfinfo->same_name_prev_id = -1;

    /* if we always add and never delete, then id == len - 1 is correct */
    if (gpa_hfinfo.len >= gpa_hfinfo.allocated_len) {
        if (!gpa_hfinfo.hfi) {
            gpa_hfinfo.allocated_len = PROTO_PRE_ALLOC_HF_FIELDS_MEM;
            gpa_hfinfo.hfi = (hf_info **)g_malloc(sizeof(hf_info *)*PROTO_PRE_ALLOC_HF_FIELDS_MEM);
            /* The entry with index 0 is not used. */
            gpa_hfinfo.hfi[0] = NULL;
            gpa_hfinfo.len = 1;
        } else {
            gpa_hfinfo.allocated_len += 1000;
            gpa_hfinfo.hfi = (hf_info **)g_realloc(gpa_hfinfo.hfi,
                           sizeof(hf_info *)*gpa_hfinfo.allocated_len);
            /*printf("gpa_hfinfo.allocated_len %u", gpa_hfinfo.allocated_len);*/
        }
    }
    gpa_hfinfo.hfi[gpa_hfinfo.len] = hfinfo;
    gpa_hfinfo.len++;
    hfinfo->id = gpa_hfinfo.len - 1;

    /* if we have real names, enter this field in the name tree */
    if ((hfinfo->name[0] != 0) && (hfinfo->abbrev[0] != 0 )) {

        hf_info *same_name_next_hfinfo;
        unsigned char c;

        /* Check that the filter name (abbreviation) is legal;
         * it must contain only alphanumerics, '-', "_", and ".". */
        c = proto_check_field_name(hfinfo->abbrev);
        if (c) {
            if (c == '.') {
                REPORT_DISSECTOR_BUG("Invalid leading, duplicated or trailing '.' found in filter name '%s'", hfinfo->abbrev);
            } else if (g_ascii_isprint(c)) {
                REPORT_DISSECTOR_BUG("Invalid character '%c' in filter name '%s'", c, hfinfo->abbrev);
            } else {
                REPORT_DISSECTOR_BUG("Invalid byte \\%03o in filter name '%s'", c, hfinfo->abbrev);
            }
        }

        /* We allow multiple hfinfo's to be registered under the same
         * abbreviation. This was done for X.25, as, depending
         * on whether it's modulo-8 or modulo-128 operation,
         * some bitfield fields may be in different bits of
         * a byte, and we want to be able to refer to that field
         * with one name regardless of whether the packets
         * are modulo-8 or modulo-128 packets. */

        same_name_hfinfo = NULL;

        g_hash_table_insert(gpa_name_map, (void *) (hfinfo->abbrev), hfinfo);
        /* GLIB 2.x - if it is already present
         * the previous hfinfo with the same name is saved
         * to same_name_hfinfo by value destroy callback */
        if (same_name_hfinfo) {
            /* There's already a field with this name.
             * Put the current field *before* that field
             * in the list of fields with this name, Thus,
             * we end up with an effectively
             * doubly-linked-list of same-named hfinfo's,
             * with the head of the list (stored in the
             * hash) being the last seen hfinfo.
             */
            same_name_next_hfinfo =
                same_name_hfinfo->same_name_next;

            hfinfo->same_name_next = same_name_next_hfinfo;
            if (same_name_next_hfinfo)
                same_name_next_hfinfo->same_name_prev_id = hfinfo->id;

            same_name_hfinfo->same_name_next = hfinfo;
            hfinfo->same_name_prev_id = same_name_hfinfo->id;
#ifdef ENABLE_CHECK_FILTER
            while (same_name_hfinfo) {
                if (_ftype_common(hfinfo->type) != _ftype_common(same_name_hfinfo->type))
                    printf("'%s' exists multiple times with incompatible types: %s and %s", hfinfo->abbrev, ftype_name(hfinfo->type), ftype_name(same_name_hfinfo->type));
                same_name_hfinfo = same_name_hfinfo->same_name_next;
            }
#endif
        }
    }

    return hfinfo->id;
}



#include "proto_hf_i.c"



void proto_register_subtree_array(int * const *indices, const int num_indices)
{
    int    i;
    int    *const *ptr = indices;

    /*
     * If we've already allocated the array of tree types, expand
     * it; this lets plugins such as mate add tree types after
     * the initial startup.  (If we haven't already allocated it,
     * we don't allocate it; on the first pass, we just assign
     * ett values and keep track of how many we've assigned, and
     * when we're finished registering all dissectors we allocate
     * the array, so that we do only one allocation rather than
     * wasting CPU time and memory by growing the array for each
     * dissector that registers ett values.)
     */
    if (tree_is_expanded != NULL) {
        tree_is_expanded = (uint32_t *)g_realloc(tree_is_expanded, (1+((num_tree_types + num_indices)/32)) * sizeof(uint32_t));

        /* set new items to 0 */
        /* XXX, slow!!! optimize when needed (align 'i' to 32, and set rest of uint32_t to 0) */
        for (i = num_tree_types; i < num_tree_types + num_indices; i++)
            tree_is_expanded[i >> 5] &= ~(1U << (i & 31));
    }

    /*
     * Assign "num_indices" subtree numbers starting at "num_tree_types",
     * returning the indices through the pointers in the array whose
     * first element is pointed to by "indices", and update
     * "num_tree_types" appropriately.
     */
    for (i = 0; i < num_indices; i++, ptr++, num_tree_types++) {
        if (**ptr != -1 && **ptr != 0) {
            REPORT_DISSECTOR_BUG("register_subtree_array: subtree item type (ett_...) not -1 or 0 !"
                " This is a development error:"
                " Either the subtree item type has already been assigned or"
                " was not initialized to -1 or 0.");
        }
        **ptr = num_tree_types;
    }
}

const char *proto_registrar_get_name(const int n)
{
    hf_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(n, hfinfo);
    return hfinfo->name;
}

const char *proto_registrar_get_abbrev(const int n)
{
    hf_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(n, hfinfo);
    return hfinfo->abbrev;
}

enum ftenum proto_registrar_get_ftype(const int n)
{
    hf_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(n, hfinfo);
    return hfinfo->type;
}

int proto_registrar_get_parent(const int n)
{
    hf_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(n, hfinfo);
    return hfinfo->parent;
}

bool proto_registrar_is_protocol(const int n)
{
    hf_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(n, hfinfo);
    return (((hfinfo->id != hf_text_only) && (hfinfo->parent == -1)) ? true : false);
}

typedef struct {
    unsigned        offset;
    field_info *finfo;
    tvbuff_t   *tvb;
} offset_search_t;

static bool check_for_offset(proto_node *node, void * data)
{
    field_info    *fi        = PNODE_FINFO(node);
    offset_search_t    *offsearch = (offset_search_t *)data;

    /* !fi == the top most container node which holds nothing */
    if (fi && !proto_item_is_hidden(node) && !proto_item_is_generated(node) && fi->ds_tvb && offsearch->tvb == fi->ds_tvb) {
        if (offsearch->offset >= (unsigned) fi->start &&
                offsearch->offset < (unsigned) (fi->start + fi->length)) {

            offsearch->finfo = fi;
            return false; /* keep traversing */
        }
    }
    return false; /* keep traversing */
}

/* Search a proto_tree backwards (from leaves to root) looking for the field
 * whose start/length occupies 'offset' */
/* XXX - I couldn't find an easy way to search backwards, so I search
 * forwards, w/o stopping. Therefore, the last finfo I find will the be
 * the one I want to return to the user. This algorithm is inefficient
 * and could be re-done, but I'd have to handle all the children and
 * siblings of each node myself. When I have more time I'll do that.
 * (yeah right) */
field_info *proto_find_field_from_offset(proto_tree *tree, unsigned offset, tvbuff_t *tvb)
{
    offset_search_t    offsearch;

    offsearch.offset = offset;
    offsearch.finfo  = NULL;
    offsearch.tvb    = tvb;

    proto_tree_traverse_pre_order(tree, check_for_offset, &offsearch);

    return offsearch.finfo;
}

bool tree_expanded(int tree_type)
{
    if (tree_type <= 0) {
        return false;
    }
//    ws_assert(tree_type >= 0 && tree_type < num_tree_types);
    return tree_is_expanded[tree_type >> 5] & (1U << (tree_type & 31));
}

void tree_expanded_set(int tree_type, bool value)
{
//    ws_assert(tree_type >= 0 && tree_type < num_tree_types);

    if (value)
        tree_is_expanded[tree_type >> 5] |= (1U << (tree_type & 31));
    else
        tree_is_expanded[tree_type >> 5] &= ~(1U << (tree_type & 31));
}


unsigned char proto_check_field_name(const char *field_name)
{
    return module_check_valid_name(field_name, false);
}

unsigned char proto_check_field_name_lower(const char *field_name)
{
    return module_check_valid_name(field_name, true);
}

/* This function indicates whether it's possible to construct a
 * "match selected" display filter string for the specified field,
 * returns an indication of whether it's possible, and, if it's
 * possible and "filter" is non-null, constructs the filter and
 * sets "*filter" to point to it.
 * You do not need to [g_]free() this string since it will be automatically
 * freed once the next packet is dissected.
 */
static bool construct_match_selected_string(const field_info *finfo, lua_dissector_t *edt,
                char **filter)
{
    const hf_info *hfinfo;
    char		  *ptr;
    int		   buf_len;
    int		   i;
    int		   start, length, length_remaining;
    uint8_t		   c;

    if (!finfo)
        return false;

    hfinfo     = finfo->hfinfo;
    DISSECTOR_ASSERT(hfinfo);

    /* If we have BASE_NONE and strings (a non-NULL FIELDCONVERT),
     * then "the numeric value ... is not used when preparing
     * filters for the field in question." If it's any other
     * base, we'll generate the filter normally (which will
     * be numeric, even though the human-readable string does
     * work for filtering.)
     *
     * XXX - It might be nice to use fvalue_to_string_repr() in
     * "proto_item_fill_label()" as well, although, there, you'd
     * have to deal with the base *and* with resolved values for
     * addresses.
     *
     * Perhaps in addition to taking the repr type (DISPLAY
     * or DFILTER) and the display (base), fvalue_to_string_repr()
     * should have the the "strings" values in the header_field_info
     * structure for the field as a parameter, so it can have
     * if the field is Boolean or an enumerated integer type,
     * the tables used to generate human-readable values.
     */
    if (hfinfo->strings && FIELD_DISPLAY(hfinfo->display) == BASE_NONE) {
        const char *str = NULL;

        switch (hfinfo->type) {

        case FT_INT8:
        case FT_INT16:
        case FT_INT24:
        case FT_INT32:
            str = hf_try_val_to_str(fvalue_get_sinteger(finfo->value), hfinfo);
            break;

        case FT_CHAR:
        case FT_UINT8:
        case FT_UINT16:
        case FT_UINT24:
        case FT_UINT32:
            str = hf_try_val_to_str(fvalue_get_uinteger(finfo->value), hfinfo);
            break;

        default:
            break;
        }

        if (str != NULL && filter != NULL) {
            *filter = wmem_strdup_printf("%s == \"%s\"", hfinfo->abbrev, str);
            return true;
        }
    }

    switch (hfinfo->type) {

        case FT_PROTOCOL:
            if (filter != NULL)
                *filter = wmem_strdup(finfo->hfinfo->abbrev);
            break;

        case FT_NONE:
            /*
             * If the length is 0, just match the name of the
             * field.
             *
             * (Also check for negative values, just in case,
             * as we'll cast it to an unsigned value later.)
             */
            length = finfo->length;
            if (length == 0) {
                if (filter != NULL)
                    *filter = wmem_strdup(finfo->hfinfo->abbrev);
                break;
            }
            if (length < 0)
                return false;

            /*
             * This doesn't have a value, so we'd match
             * on the raw bytes at this address.
             *
             * Should we be allowed to access to the raw bytes?
             * If "edt" is NULL, the answer is "no".
             */
            if (edt == NULL)
                return false;

            /*
             * Is this field part of the raw frame tvbuff?
             * If not, we can't use "frame[N:M]" to match
             * it.
             *
             * XXX - should this be frame-relative, or
             * protocol-relative?
             *
             * XXX - does this fallback for non-registered
             * fields even make sense?
             */
            if (finfo->ds_tvb != edt->tvb)
                return false;	/* you lose */

            /*
             * Don't go past the end of that tvbuff.
             */
            length_remaining = tvb_captured_length_remaining(finfo->ds_tvb, finfo->start);
            if (length > length_remaining)
                length = length_remaining;
            if (length <= 0)
                return false;

            if (filter != NULL) {
                start = finfo->start;
                buf_len = 32 + length * 3;
                *filter = (char *)wmem_alloc0(buf_len);
                ptr = *filter;

                ptr += snprintf(ptr, buf_len-(ptr-*filter),
                    "frame[%d:%d] == ", finfo->start, length);
                for (i=0; i<length; i++) {
                    c = tvb_get_uint8(finfo->ds_tvb, start);
                    start++;
                    if (i == 0 ) {
                        ptr += snprintf(ptr, buf_len-(ptr-*filter), "%02x", c);
                    }
                    else {
                        ptr += snprintf(ptr, buf_len-(ptr-*filter), ":%02x", c);
                    }
                }
            }
            break;

        /* By default, use the fvalue's "to_string_repr" method. */
        default:
            if (filter != NULL) {
                char *str = fvalue_to_string_repr(finfo->value, FTREPR_DFILTER, finfo->hfinfo->display);
                *filter = wmem_strdup_printf("%s == %s", hfinfo->abbrev, str);
                wmem_free(str);
            }
            break;
    }

    return true;
}

/*
 * Returns true if we can do a "match selected" on the field, false
 * otherwise.
 */
bool proto_can_match_selected(const field_info *finfo, lua_dissector_t *edt)
{
    return construct_match_selected_string(finfo, edt, NULL);
}

/* This function attempts to construct a "match selected" display filter
 * string for the specified field; if it can do so, it returns a pointer
 * to the string, otherwise it returns NULL.
 *
 * The string is wmem allocated and must be freed with "wmem_free(NULL, ...)".
 */
char *proto_construct_match_selected_string(const field_info *finfo, lua_dissector_t *edt)
{
    char *filter = NULL;

    if (!construct_match_selected_string(finfo, edt, &filter))
    {
        wmem_free(filter);
        return NULL;
    }
    return filter;
}
